<?php

class Core_Route {
	
	const REGEX_KEY     = '<([a-zA-Z0-9_]++)>';
	const REGEX_SEGMENT = '[^/.,;?\n]++';
	const REGEX_ESCAPE  = '[.\+*?[^\]${}=!|]';

	protected $_regex    = '';
	protected $_uri      = '';
	protected $_defaults = array();
	protected $_callback = false;

	public function __construct($uri, $callback = false) {
		$this->_uri      = $uri;
		$this->_callback = $callback;
		$this->_compile();
	}

	public function callback($callback = null) {
		if ($callback === null)
			return $this->_callback;

		$this->_callback = $callback;
	}

	public function defaults(array $defaults) {
		$this->_defaults = $defaults;

		return $this;
	}

	public function matches($uri) {
		if (!preg_match($this->_regex, $uri, $matches))
	        return false;
	 
	    $params = array();
	    foreach ($matches as $key => $value) {
	        if (is_int($key)) {
	            // Skip all unnamed keys
	            continue;
	        }
	 
	        // Set the value for all matched keys
	        $params[$key] = $value;
	    }
	 
	    foreach ($this->_defaults as $key => $value) {
	        if (!isset($params[$key]) OR $params[$key] === '') {
	            // Set default values for any key that was not matched
	            $params[$key] = $value;
	        }
	    }
	 
	    return $params;
	}

	protected function _compile() {
		// The URI should be considered literal except for keys and optional parts
	    // Escape everything preg_quote would escape except for : ( ) < >
	    $regex = preg_replace('#'.Route::REGEX_ESCAPE.'#', '\\\\$0', $this->_uri);
	 
	    if (strpos($regex, '(') !== FALSE)
	    {
	        // Make optional parts of the URI non-capturing and optional
	        $regex = str_replace(array('(', ')'), array('(?:', ')?'), $regex);
	    }
	 
	    // Insert default regex for keys
	    $regex = str_replace(array('<', '>'), array('(?P<', '>'.Route::REGEX_SEGMENT.')'), $regex);
	 
	    $this->_regex = '#^'.$regex.'$#uD';
	}

}